﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using DisplayBoardInfo.Network.Messages;

namespace DisplayBoardInfo.Network
{
    public class Server
    {
        private TcpListener listener;
        private Dictionary<int, TcpClient> clients;
        private int bufferSize = short.MaxValue;

        public delegate void MessageRecivedHandler(object sender, MessageEventArgs args);
        public event MessageRecivedHandler MessageRecived;
        public event MessageRecivedHandler ClientDisconnected;

        private bool isRunning = false;

        /// <summary>
        /// Gets status of server.
        /// </summary>
        public bool IsRunning
        {
            get { return isRunning; }
        }

        public Server()
        {
            this.MessageRecived += new MessageRecivedHandler(Server_MessageRecived);
        }

        /// <summary>
        /// Starts server in new thread.
        /// </summary>
        public void Start()
        {
            try
            {
                isRunning = true;
                clients = new Dictionary<int, TcpClient>();
                listener = new TcpListener(IPAddress.Any, 9000);
                listener.Server.SendTimeout = 0;
                listener.Server.ReceiveTimeout = 0;
                new Thread(
                    delegate()
                    {
                        listener.Start();
                        while (isRunning)
                        {
                            try
                            {
                                TcpClient client = listener.AcceptTcpClient();
                                AddClient(client);
                            }
                            catch (SocketException)
                            { 
                                //ignore, propably someone forcely turns of the application
                            }
                            
                        }
                    }
                ).Start();
            }
            catch (SocketException ex)
            {
                throw new ServerException(ex.Message);
            }
            catch (InvalidOperationException)
            {
                throw new ServerException();
            }
        }

        /// <summary>
        /// Stops running server.
        /// </summary>
        public void Stop()
        {
            isRunning = false;
            foreach (TcpClient client in clients.Values)
            {
                if (client.Connected) client.Close();
            }
            //clients = new Dictionary<int, TcpClient>();
            listener.Stop();
        }

        /// <summary>
        /// Sends message to all clients.
        /// </summary>
        /// <param name="message">Message to send.</param>
        public void SendToAll(Network.Messages.NetworkMessage message)
        {
            foreach (TcpClient client in new List<TcpClient>(clients.Values))
            {
                NetworkStream stream = null;
                try
                {
                    if (client.Connected)
                    {
                        stream = client.GetStream();

                        MemoryStream memStream = new MemoryStream();

                        BinaryFormatter bf = new BinaryFormatter();
                        bf.Serialize(memStream, message);

                        byte[] byteMessage = memStream.GetBuffer();

                        stream.Write(byteMessage, 0, byteMessage.Length);
                        stream.Flush();
                    }

                }
                catch (Exception)
                {
                    stream.Close();
                    if (!client.Connected) DeleteClient(client);
                }
            }
        }

        /// <summary>
        /// Sends message to specyfic client
        /// </summary>
        /// <param name="clientID">Client's ID to send message</param>
        /// <param name="message">Message to send</param>
        /// <exception cref="ArgumentOutOfRangeException">Client is not exist.</exception>
        /// <exception cref="ServerException">Error during send</exception>
        public void Send(int clientID, Network.Messages.NetworkMessage message)
        {
            if (clients.Keys.Contains(clientID))
            {
                NetworkStream stream = null;
                try
                {
                    if (clients[clientID].Connected)
                    {
                        stream = clients[clientID].GetStream();

                        MemoryStream memStream = new MemoryStream();

                        BinaryFormatter bf = new BinaryFormatter();
                        bf.Serialize(memStream, message);

                        byte[] byteMessage = memStream.GetBuffer();

                        stream.Write(byteMessage, 0, byteMessage.Length);
                        stream.Flush();
                    }

                }
                catch (Exception ex)
                {
                    throw new ServerException(ex.Message);
                }
            }
            else throw new ArgumentOutOfRangeException("Client is not exist.");
        }

        private void AddClient(TcpClient client)
        {
            //TODO: Naprawić to, bo to śmieszne :)
            

            int clientID = clients.Count;
            clients.Add(clientID, client);

            new Thread(
                delegate()
                {
                    NetworkStream stream = client.GetStream();

                    Byte[] bytes = new Byte[bufferSize];

                    while (client.Connected)
                    {
                        int i;
                        try
                        {
                            //if (bufferSize != bytes.Length) bytes = new Byte[bufferSize]; //Sometimes bufferSize will change
                            while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
                            {
                                MemoryStream ms = new MemoryStream(bytes);
                                ms.Position = 0;
                                BinaryFormatter bf = new BinaryFormatter();
                                NetworkMessage message = (NetworkMessage)bf.Deserialize(ms);
                                Thread t = new Thread(delegate()
                                    {
                                        HandleMessage(clientID, message);
                                    });

                                t.SetApartmentState(ApartmentState.STA);
                                t.Start();
                                
                            }
                        }
                        catch (System.Runtime.Serialization.SerializationException ex)
                        {
                            Logger.Logger.Message(Logger.LogMessageType.Error, ex.Message);
                        }
                        catch (ArgumentNullException ex)
                        {
                            Logger.Logger.Message(Logger.LogMessageType.Error, ex.Message);
                        }
                        catch (Exception ex)
                        {
                            stream.Close();
                            if (client.Connected) client.Close();
                            Logger.Logger.Message(Logger.LogMessageType.Info, ex.Message);
                            ClientDisconnected(this, new MessageEventArgs(clientID, null));
                            break;
                        }
                    }
                }
            ).Start();
        }


        private void DeleteClient(TcpClient client)
        {
            foreach (int id in new List<int>(clients.Keys))
            {
                if (clients[id].Equals(client)) DeleteClient(id);
            }
        }

        private void DeleteClient(int id)
        {
            if (clients[id].Connected) clients[id].Close();
            clients.Remove(id);
        }
       
        private void HandleMessage(int clientID, Network.Messages.NetworkMessage message)
        {
            //if (message.Type == NetworkMessageTypes.Text && message.Content.ToString().Length > 3 && message.Content.ToString().Substring(0, 3) == "#s#") //stering message
            //{
            //    int a;
            //    if (Int32.TryParse(message.Content.ToString().Substring(3), out a))
            //    {
            //        bufferSize = a;
            //    }
            //}
            //else 
            this.
            MessageRecived(this, new MessageEventArgs(clientID, message));
        }

        void Server_MessageRecived(object sender, MessageEventArgs args)
        {
            //default implementation of event
        }

    }
}
